/**
 * The handler package.
 */
package fr.umlv.m2.ig.handler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import fr.umlv.m2.ig.model.BeauheurModelImpl;
import fr.umlv.m2.ig.model.Journey;
import fr.umlv.m2.ig.model.Line;
import fr.umlv.m2.ig.model.Station;
import fr.umlv.m2.ig.model.StationState;
import fr.umlv.m2.ig.model.Train;

/**
 * This class defines the configuration handler of the application.
 * 
 * @author Gambouele Bertrand.
 * @author Viravout Alexandre.
 */
public class ConfigurationHandler extends DefaultHandler {
	/**
	 * This enumeration defines the enable actions with the configuration.
	 *  
	 * @author Gambouele Bertrand.
	 * @author VIravout Alexandre.
	 */
	public enum ConfigAction { LOAD, SAVE };

	/**
	 * The file used to load or save configuration.
	 */
	private File file;

	/**
	 * The current action called.
	 */
	private ConfigAction action;

	/**
	 * The model.
	 */
	private final BeauheurModelImpl model;

	/**
	 * Currently parsed station.
	 */
	private String currentStation;

	/**
	 * Creates a new {@code ConfigurationHandler} object.
	 * 
	 * @param model the model.
	 */
	public ConfigurationHandler(BeauheurModelImpl model) {
		this.model = model;
	}

	/**
	 * Sets the file to load or save the configuration.
	 * 
	 * @param file the file.
	 */
	public void setFile(File file) {
		this.file = file;
	}

	/**
	 * Sets the current action to do.
	 * 
	 * @param actionX action to do with the configuration.
	 */
	public void setAction(ConfigAction actionX) {
		this.action = actionX;
	}

	/**
	 * Gets the current action to do.
	 * 
	 * @return the current action to do with the configuration.
	 */
	public ConfigAction getAction() {
		return this.action;
	}

	/**
	 * Load the application configuration.
	 */
	public void loadConfig() {
		if (file == null) {
			return;
		}
		
		XMLReader reader = null;
		InputStream colorsFile = null;
		
		try {
			reader = XMLReaderFactory.createXMLReader();
			
			reader.setContentHandler(this);

			colorsFile = new FileInputStream(file);
			InputSource source = new InputSource(colorsFile);

			model.lock();
			model.resetTrainsList();source.setEncoding("UTF-8");
			reader.parse(source);
			model.searchDisplayStationsByName("");
		} catch (SAXException e) {
			System.out.println("An error occured while attempting to read config file. The program will exit...");
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			System.out.println("An error occured while attempting to read config file. The program will exit...");
			e.printStackTrace();
			System.exit(1);
		} finally {
			try {
				model.unlock();
				colorsFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Save the application configuration.
	 */
	public void saveConfig() {
		if (file != null) {
			FileWriter fw = null;

			try {
				fw = new FileWriter(this.file);
			} catch (IOException e) {
				System.out.println("An error occured when attempting to save config file ...");
				e.printStackTrace();
				return;
			}

			try {
				model.lock();
				fw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n");
				fw.write("<metroConfig>\r\n\r\n");
				
				saveLines(fw);
				saveStations(fw);
				saveTrains(fw);
				
				fw.write("\r\n</metroConfig>");
			} catch (IOException e) {
				System.out.println("An error occured when attempting to save config file ...");
				e.printStackTrace();
				return;
			} finally {
				model.unlock();

				try {
					fw.flush();
					fw.close();
				} catch (IOException e) {
					System.out.println("An error occured when attempting to close the config file ...");
					e.printStackTrace();
					return;
				}
			}
		}
	}

	/**
	 * Writes trains in the config file.
	 * 
	 * @param fw the config file writer.
	 * @throws IOException if an error occurs while writing.
	 */
	private void saveTrains(FileWriter fw) throws IOException {
		StringBuilder sb = new StringBuilder();
		sb.append("<trains>\r\n\r\n");

		List<Train> trains = model.getTrains();

		for (Train train : trains) {
			sb.append("\t<train id=\"").append(train.getId()).append("\" ");
			sb.append("line=\"").append(train.getLine()).append("\" ");
			sb.append("origin=\"").append(train.getOrigin()).append("\" ");
			sb.append("destination=\"").append(train.getDestination()).append("\" ");
			sb.append("currentStation=\"").append(train.getCurrentStation()).append("\" ");
			sb.append("nextStation=\"").append(train.getNextStation()).append("\" ");
			sb.append("positionX=\"").append(train.getPositionX()).append("\" ");
			sb.append("positionY=\"").append(train.getPositionY()).append("\" ");
			sb.append("waitingTime=\"").append(train.getWaitingTime()).append("\" ");
			sb.append("remainingMoves=\"").append(train.getRemainingMoves()).append("\" ");
			sb.append("moveStepX=\"").append(train.getMoveStepX()).append("\" ");
			sb.append("moveStepY=\"").append(train.getMoveStepY()).append("\" />\r\n");
			fw.write(sb.toString());
			sb.setLength(0);
		}

		sb.append("</trains>\r\n");
		fw.write(sb.toString());
	}

	/**
	 * Writes stations in the config file.
	 * 
	 * @param fw the config file writer.
	 * @throws IOException if an error occurs while writing.
	 */
	private void saveStations(FileWriter fw) throws IOException {
		StringBuilder sb = new StringBuilder();
		sb.append("<stations>\r\n\r\n");

		Map<String, Line> metroLines = model.getLines();
		Map<String, Station> stations = model.getStations();

		for (Station station : stations.values()) {
			sb.append("\t<station name=\"").append(station.getName()).append("\">\r\n");
			Collection<String> lines = station.getLines();

			for (String lineName : lines) {
				sb.append("\t\t<state line=\"").append(lineName);
				sb.append("\" value=\"").append(station.getState(lineName)).append("\" />\r\n");
				
				sb.append("\t\t<waitingTime line=\"").append(lineName);
				sb.append("\" value=\"").append(model.getWaitingTime(station.getName(), lineName));
				sb.append("\" />\r\n");

				List<Journey> journeys = metroLines.get(lineName).getJourneys();

				for (Journey j : journeys) {
					String nextStation = station.getNextStation(lineName, j.getDestination());

					if (nextStation != null) {
						boolean busy = station.isBusy(lineName, j.getDestination());

						sb.append("\t\t<busy line=\"").append(lineName);
						sb.append("\" destination=\"").append(j.getDestination());
						sb.append("\" value=\"").append((busy ? "true" : "false")).append("\" />\r\n");
					}
				}
			}

			sb.append("\t</station>\r\n\r\n");
			fw.write(sb.toString());
			sb.setLength(0);
		}

		sb.append("</stations>\r\n\r\n\r\n");
		fw.write(sb.toString());
	}

	/**
	 * Writes lines in the config file.
	 * 
	 * @param fw the config file writer.
	 * @throws IOException if an error occurs while writing.
	 */
	private void saveLines(FileWriter fw) throws IOException {
		StringBuilder sb = new StringBuilder();
		Map<String, Line> lines = model.getLines();

		sb.append("<lines>\r\n\r\n");

		for (Line line : lines.values()) {
			sb.append("\t<line name=\"" + line.getName() + "\" speed=\"" + line.getSpeed() + "\" />\r\n");
			fw.write(sb.toString());
			sb.setLength(0);
		}

		sb.append("\r\n</lines>\r\n\r\n\r\n");
		fw.write(sb.toString());
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if ("line".equals(qName)) {
			double speed = Double.parseDouble(attributes.getValue("speed"));
			
			model.setTrainSpeed(attributes.getValue("name"), speed);
			
		} else if ("station".equals(qName)) {
			this.currentStation = attributes.getValue("name");
			
		} else if ("state".equals(qName)) {
			StationState state = StationState.valueOf(attributes.getValue("value"));
			
			model.setStationState(this.currentStation, attributes.getValue("line"), state);
			
		} else if ("waitingTime".equals(qName)) {
			double waitingTime = Double.parseDouble(attributes.getValue("value"));
			
			model.setWaitingTime(this.currentStation, attributes.getValue("line"), waitingTime);
			
		} else if ("busy".equals(qName)) {
			String line = attributes.getValue("line");
			String destination = attributes.getValue("destination");
			boolean busy = Boolean.parseBoolean(attributes.getValue("value"));
			
			model.setStationBusy(this.currentStation, line, destination, busy);
			
		} else if ("train".equals(qName)) {
			String trainId = attributes.getValue("id");
			String trainLine = attributes.getValue("line");
			String startStation = attributes.getValue("currentStation");
			String destination1 = attributes.getValue("destination");
			String destination2 = attributes.getValue("origin");

			Train train = new Train(trainId, trainLine, startStation, destination1, destination2);

			model.addTrainFromConfigFile(train);
			model.setTrainNextStation(train, attributes.getValue("nextStation"));
			
			double positionX = Double.parseDouble(attributes.getValue("positionX"));
			double positionY = Double.parseDouble(attributes.getValue("positionY"));
			
			model.setTrainPosition(train, positionX, positionY);
			
			model.setTrainWaitingTime(train, Integer.parseInt(attributes.getValue("waitingTime")));
			model.setTrainRemainingMoves(train, Integer.parseInt(attributes.getValue("remainingMoves")));
			model.setTrainMoveStepX(train, Double.parseDouble(attributes.getValue("moveStepX")));
			model.setTrainMoveStepY(train, Double.parseDouble(attributes.getValue("moveStepY")));
		}
	}
}
